package com.dpstudio.ymprestlful.utils.convert;


import cn.hutool.core.map.MapUtil;
import com.dpstudio.ymprestlful.annotation.YmpHttpMethodAnnotation;
import com.dpstudio.ymprestlful.utils.JsonUtil;
import com.dpstudio.ymprestlful.utils.RestUtil;
import com.intellij.lang.jvm.annotation.JvmAnnotationAttribute;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
 * @Author: mengxiang.
 * @Date: 2020/6/22.
 * @Time: 11:37 上午.
 * @Description:
 */
public class ParamConvert {

    private PsiMethod psiMethod;


    public ParamConvert() {
    }

    public ParamConvert(@NotNull PsiMethod psiMethod) {
        this.psiMethod = psiMethod;
    }

    public void setPsiMethod(@NotNull PsiMethod psiMethod) {
        this.psiMethod = psiMethod;
    }


    /**
     * parse method param
     *
     * @return map
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Object> parseMethodParams() {
        if (psiMethod == null) {
            return Collections.emptyMap();
        }

        PsiParameterList parameterList = psiMethod.getParameterList();
        if (parameterList.isEmpty()) {
            return Collections.emptyMap();
        }

        String qualifiedName = YmpHttpMethodAnnotation.REQUEST_PARAM.getQualifiedName();
        Map<String, Object> map = new HashMap<>();
        for (PsiParameter parameter : parameterList.getParameters()) {
            PsiAnnotation[] parameterAnnotations = parameter.getAnnotations();
            String parameterName = parameter.getName();
            PsiType parameterType = parameter.getType();

            boolean hasRequestParamAnnotation = false;
            for (PsiAnnotation parameterAnnotation : parameterAnnotations) {
                if (!qualifiedName.equals(parameterAnnotation.getQualifiedName())) {
                    continue;
                }
                List<JvmAnnotationAttribute> attributes = parameterAnnotation.getAttributes();
                for (JvmAnnotationAttribute attribute : attributes) {
                    String name = attribute.getAttributeName();
                    if (!("name".equals(name) || "value".equals(name))) {
                        continue;
                    }
                    Object value = RestUtil.getAttributeValue(attribute.getAttributeValue());
                    if (value != null) {
                        parameterName = value.toString();
                        hasRequestParamAnnotation = !hasRequestParamAnnotation;
                    }
                }
            }

            Object paramDefaultTypeValue = getTypeDefaultData(psiMethod, parameterType);
            if (paramDefaultTypeValue != null) {
                if (paramDefaultTypeValue instanceof Map) {
                    //noinspection unchecked,rawtypes
                    Map<String, Object> value = (Map) paramDefaultTypeValue;
                    value.forEach(map::put);
                } else {
                    map.put(parameterName, paramDefaultTypeValue);
                }
            }
        }
        return map;
    }

    private String[] TYPE = {"psitype:string","psitype:char","psitype:character","psitype:byte","psitype:short","psitype:int"
            ,"psitype:integer","psitype:long","psitype:float","psitype:double","psitype:boolean"};

    @Nullable
    private Object getTypeDefaultData(@NotNull PsiMethod method, PsiType parameterType) {
        if (parameterType instanceof PsiArrayType) {
            return Collections.emptyList();
        }else if ( Arrays.asList(TYPE).contains(parameterType.toString().toLowerCase())) {
//            PsiPrimitiveType type = (PsiPrimitiveType) parameterType;
            String parameterTypeName = parameterType.toString();
            parameterTypeName = parameterTypeName.substring(parameterType.toString().indexOf(":")+1,parameterType.toString().length());
            return getDefaultData(parameterTypeName);
        } else if (parameterType instanceof PsiClassReferenceType) {
            PsiClassReferenceType type = (PsiClassReferenceType) parameterType;
            GlobalSearchScope resolveScope = type.getResolveScope();
            PsiFile[] psiFiles = FilenameIndex.getFilesByName(
                    method.getProject(),
                    type.getName() + ".java",
                    resolveScope
            );
            if (psiFiles.length > 0) {
                for (PsiFile psiFile : psiFiles) {
                    if (psiFile instanceof PsiJavaFile) {
                        PsiClass[] fileClasses = ((PsiJavaFile) psiFile).getClasses();
                        Map<String, Object> map = new HashMap<>();
                        for (PsiClass psiClass : fileClasses) {
                            if (type.getReference().getQualifiedName().equals(psiClass.getQualifiedName())) {
                                PsiField[] fields = psiClass.getFields();
                                for (PsiField field : fields) {
                                    String fieldName = field.getName();
                                    Object defaultData = getTypeDefaultData(method, field.getType());
                                    map.put(fieldName, defaultData);
                                }
                                break;
                            }
                        }
                        return map;
                    }
                }
            } else {
                return getDefaultData(type.getName());
            }
        }
        return null;
    }

    @Contract(pure = true)
    private Object getDefaultData(@NotNull String classType) {
        Object data = null;
        switch (classType.toLowerCase()) {
            case "string":
                data = "demoData";
                break;
            case "char":
            case "character":
                data = 'A';
                break;
            case "byte":
            case "short":
            case "int":
                data = 0;
            case "integer":
            case "long":
                data = 0L;
                break;
            case "float":
            case "double":
                data = 0D;
                break;
            case "boolean":
                data = true;
                break;
            default:
                break;
        }
        return data;
    }

    /**
     * get method'param to convert show String
     *
     * @return str
     */
    public String formatString() {
        Map<String, Object> methodParams = parseMethodParams();
        return JsonUtil.formatJson(methodParams);
    }

    /**
     * 返回方法参数map
     * @return
     */
    public Map<String, Object> parseMethodParamsMap() {
        return parseMethodParams();
    }

    /**
     * 返回方法参数字符串
     * @return
     */
    public String parseMethodParamsStr() {
        Map<String,Object> paramsMap = parseMethodParams();
        if (MapUtil.isEmpty(paramsMap)) {
            return "";
        }
        StringBuilder stringBuilder = new StringBuilder();
        paramsMap.forEach((k, v) -> {
            stringBuilder.append(k + "=" + v + "&");
        });
        String paramStr = stringBuilder.toString();
        if(paramStr.endsWith("&")){
            paramStr = StringUtils.substringBeforeLast(paramStr, "&");
        }
        return paramStr;
    }

    /**
     * parse show String to convert Key-Value
     *
     * @param paramsStr show string
     * @return map
     */
    public Map<String, Object> formatMap(@NotNull String paramsStr) {
        return (Map<String, Object>) JsonUtil.formatMap(paramsStr);
    }
}
